﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
using System.Windows.Media.Imaging;
using System.Diagnostics;

namespace BitmapTagger
{
	/// <summary>
	/// Represents the tags of a PNG
	/// </summary>
	public class PngTagger : BitmapTagger
	{

		#region constants

		/// <summary>
		/// Query used to get/set the XMP XML
		/// </summary>
		/// <remarks>
		/// Currently uses text, but will need to be switched to 
		/// text once WIC supports itxt writing
		/// </remarks>
		private const string XMP_XML_QUERY = "/Text/XML:com.adobe.xmp";


		#endregion

		/// <summary>
		/// The XML data structure that is the XMP data to be stored.
		/// </summary>
		private XDocument xmpXML;


		#region constructors

		/// <summary>
		/// Create a new jpeg tagger
		/// </summary>
		/// <param name="filePath">The file path of the jpeg image</param>
		/// <param name="loadXMPExif">Whether or not to load XMPExif tags</param>
		/// <param name="loadXMP">Whether or not to load XMP tags</param>
		/// <param name="precedence">The precedence of the tagging systems</param>
		public PngTagger(string filePath,  bool loadXMPExif, bool loadXMP,
			TagPrecedence precedence)
		{
			Precedence = precedence;
			Load(filePath, false, loadXMPExif, loadXMP, Precedence);
		}

		/// <summary>
		/// Create a new PngTagger
		/// </summary>
		/// <remarks>
		/// Defaults to load all tags
		/// Defaults Tagging System Precendence as XMP > XMPExif 
		/// </remarks>
		/// <param name="filePath">The file path of the png image</param>
		public PngTagger(string filePath) : this(filePath, true, true, TagPrecedence.XMP_XMPExif_Exif) { }

		#endregion

		#region xml properties

		/// <summary>
		/// Get or set the XML data that the XMP data is stored in
		/// </summary>
		private XDocument XmpXML
		{
			get { return xmpXML; }
			set { xmpXML = value; }
		}

		/// <summary>
		/// Get the RDF Element
		/// </summary>
		private XElement RDFElement
		{
			get
			{
				var MetadataElement = XmpXML.Element(XMPElementNames.Metadata);

				//If no metadata element is present the RDF element may be the outermost
				//element
				if (MetadataElement == null)
					return  XmpXML.Element(XMPElementNames.RDF);


				return MetadataElement.Element(XMPElementNames.RDF);
			}
		}

		/// <summary>
		/// Get the first descriptionElement
		/// </summary>
		private XElement DescriptionElement
		{
			get
			{
				var rdfElement = RDFElement;

				if (rdfElement == null)
					return null;

				return rdfElement.Element(XMPElementNames.Description);
			}
		}

		/// <summary>
		/// Get a collection of the description elements
		/// </summary>
		private IEnumerable<XElement> DescriptionElements
		{
			get
			{
				var rdfElement = RDFElement;

				if (rdfElement == null)
					return null;

				return rdfElement.Elements(XMPElementNames.Description);
			}
		}

		#endregion


		#region save

		/// <summary>
		/// Save the Image to the same file path it was loaded from
		/// </summary>
		/// <remarks>
		/// Defaults to save tags to XMPExif and XMP
		/// </remarks>
		public override void Save()
		{
			Save(false, true, true);
		}

		/// <summary>
		/// Save the Image 
		/// </summary>
		/// <param name="filePath">The file path to save to</param>
		public override void Save(string filePath)
		{
			File.Copy(FilePath, filePath, true);
			FilePath = filePath;

			Save();
		}

		/// <summary>
		/// Save the Image 
		/// </summary>
		/// <param name="filePath">The file path to save to</param>
		/// <param name="saveExif">Not used in PNGs as PNGs do not have Exif, here for inheritance reasons</param>
		/// <param name="saveXMPExif">Whether or not to save the information to XMPExif tags</param>
		/// <param name="saveXMP">Whether or not to save the information to XMP tags</param>
		public override void Save(string filePath, bool saveExif, bool saveXMPExif, bool saveXMP)
		{
			if (!filePath.Equals(FilePath))
			{
				File.Copy(FilePath, filePath, true);
				FilePath = filePath;
			}

			Save(saveExif, saveXMPExif, saveXMP);
		}

		/// <summary>
		/// Save the Image to the same file path it was loaded from
		/// </summary>
		/// <param name="saveExif">Not used in PNGs as PNGs do not have Exif, here for inheritance reasons</param>
		/// <param name="saveXMPExif">Whether or not to save the information to XMPExif tags</param>
		/// <param name="saveXMP">Whether or not to save the information to XMP tags</param>
		public override void Save(bool saveExif, bool saveXMPExif, bool saveXMP)
		{

			//If XMP data was not present to start create the new XMP data structure
			if (XmpXML == null)
			{
				XmpXML = new XDocument();
			}
			else
			{
				//Save the XMPExif data
				if (saveXMPExif)
					SaveXMPExif();

				//Save the XMP data
				if (saveXMP)
					SaveXMP();
			}

			string tempFile = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + System.IO.Path.DirectorySeparatorChar + "temp.jpg";
			using (Stream inputFile = File.Open(FilePath, FileMode.Open, FileAccess.ReadWrite))
			{
				BitmapDecoder original = BitmapDecoder.Create(inputFile, BitmapCreateOptions.None, BitmapCacheOption.Default);
				BitmapMetadata metadata = original.Frames[0].Metadata.Clone() as BitmapMetadata;

				//Recreate the image file with the new metadata
				PngBitmapEncoder output = new PngBitmapEncoder();

				metadata.SetQuery(XMP_XML_QUERY, XmpXML.ToString());

				output.Frames.Add(BitmapFrame.Create(original.Frames[0], original.Frames[0].Thumbnail, metadata, original.Frames[0].ColorContexts));
				//Must save to a temporary location as the stream to the original file is still open
				using (Stream outputFile = File.Create(tempFile))
				{
					output.Save(outputFile);
				}
			}

			//Move the temp file to be in place of the current file
			File.Copy(tempFile, FilePath, true);
			File.Delete(tempFile);


			Debug.WriteLine("Printing out the XMP XML");
			Debug.WriteLine(XmpXML.ToString());


		}


		/// <summary>
		/// Save the XMP data in the XmpXML
		/// </summary>
		private void SaveXMP()
		{
			//If description does not have xmlns prefix attributes add them
			if (GetXMPAttribute(XNamespace.Xmlns + XMPPrefixes.XMPBasicPrefix) == null)
			{
				DescriptionElement.Add(
					new XAttribute(XNamespace.Xmlns + XMPPrefixes.XMPBasicPrefix, XMPNamespaces.XMPBasicSchema.NamespaceName));
			}

			if (GetXMPAttribute(XNamespace.Xmlns + XMPPrefixes.DublinCorePrefix) == null)
			{
				DescriptionElement.Add(
					new XAttribute(XNamespace.Xmlns + XMPPrefixes.DublinCorePrefix, XMPNamespaces.DublinCoreSchema.NamespaceName));
			}

			XElement tagElement = null;
			XAttribute tagAttribute = null;

			//DateTime
			tagAttribute = GetXMPAttribute(XMPTagIDs.ModifyDateID);
			tagElement = GetXMPElement(XMPTagIDs.ModifyDateID);
			if (DateTime != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = GetXMPTagStringFromDateTime(DateTime);
				if (tagElement != null)
					tagElement.Value = GetXMPTagStringFromDateTime(DateTime);
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute dateTimeAttribute = new XAttribute(XMPTagIDs.ModifyDateID, GetXMPTagStringFromDateTime(DateTime));
					DescriptionElement.Add(dateTimeAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//Image Description 
			tagElement = GetXMPElement(XMPTagIDs.DescriptionID);
			if (tagElement != null)
			{
				//The tag does exist
				XElement englishElement = GetLangAltEnglishElement(tagElement);
				if (englishElement != null)
				{
					//The english element does exist
					if (String.IsNullOrEmpty(ImageDescription))
					{
						//No image description, remove the english element
						englishElement.Remove();
					}
					else
					{
						englishElement.Value = ImageDescription;
					}
				}
				else if(!String.IsNullOrEmpty(ImageDescription))
				{
					//English element does not exist but image description does
					AddEnglishElementToLangAltElement(tagElement, ImageDescription);
				}
			}
			else if (!String.IsNullOrEmpty(ImageDescription))
			{
				//tag does not exist but image description does, create the tag
				XElement descriptionTag = 
					CreateLangAltWithEnglishElement(XMPTagIDs.DescriptionID, ImageDescription);
				DescriptionElement.Add(descriptionTag);
			}

			//Software
			tagAttribute = GetXMPAttribute(XMPTagIDs.CreatorToolID);
			tagElement = GetXMPElement(XMPTagIDs.CreatorToolID);
			if (!String.IsNullOrEmpty(Software))
			{
				if (tagAttribute != null)
					tagAttribute.Value = Software;
				if (tagElement != null)
					tagElement.Value = Software;
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute softwareAttribute = new XAttribute(XMPTagIDs.CreatorToolID, Software);
					DescriptionElement.Add(softwareAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//Artist CreatorID
			tagAttribute = GetXMPAttribute(XMPTagIDs.CreatorID);
			tagElement = GetXMPElement(XMPTagIDs.CreatorID);
			if (!String.IsNullOrEmpty(Artist))
			{
				if (tagAttribute != null)
					tagAttribute.Value = Artist;
				if (tagElement != null)
					tagElement.Value = Artist;
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute artistAttribute = new XAttribute(XMPTagIDs.CreatorID, Artist);
					DescriptionElement.Add(artistAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}


			//Copyright RightsID
			tagElement = GetXMPElement(XMPTagIDs.RightsID);
			if (tagElement != null)
			{
				//The tag does exist
				XElement englishElement = GetLangAltEnglishElement(tagElement);
				if (englishElement != null)
				{
					//The english element does exist
					if (String.IsNullOrEmpty(Copyright))
					{
						//No image description, remove the english element
						englishElement.Remove();
					}
					else
					{
						englishElement.Value = Copyright;
					}
				}
				else if (!String.IsNullOrEmpty(Copyright))
				{
					//English element does not exist but image description does
					AddEnglishElementToLangAltElement(tagElement, Copyright);
				}
			}
			else if (!String.IsNullOrEmpty(Copyright))
			{
				//tag does not exist but image description does, create the tag
				XElement rightsTag =
					CreateLangAltWithEnglishElement(XMPTagIDs.RightsID, Copyright);
				DescriptionElement.Add(rightsTag);
			}

		}

		/// <summary>
		/// Save the XMPExif tags
		/// </summary>
		private void SaveXMPExif()
		{
			//If description does not have xmlns prefix attributes add them
			if (GetXMPAttribute(XNamespace.Xmlns + XMPPrefixes.ExifPrefix) == null)
			{
				DescriptionElement.Add(
					new XAttribute(XNamespace.Xmlns + XMPPrefixes.ExifPrefix, XMPNamespaces.ExifSchema.NamespaceName));
			}

			if (GetXMPAttribute(XNamespace.Xmlns + XMPPrefixes.TiffExifPrefix) == null)
			{
				DescriptionElement.Add(
					new XAttribute(XNamespace.Xmlns + XMPPrefixes.TiffExifPrefix, XMPNamespaces.TiffSchema.NamespaceName));
			}

			XElement tagElement = null;
			XAttribute tagAttribute = null;

			//XResolution
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.XResolutionID);
			tagElement = GetXMPElement(XMPExifTagIDs.XResolutionID);
			if (XResolution != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = XResolution.ToString();
				if (tagElement != null)
					tagElement.Value = XResolution.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute xResolutionAttribute = new XAttribute(XMPExifTagIDs.XResolutionID, XResolution.ToString());
					DescriptionElement.Add(xResolutionAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//YResolution
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.YResolutionID);
			tagElement = GetXMPElement(XMPExifTagIDs.YResolutionID);
			if (YResolution != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = YResolution.ToString();
				if (tagElement != null)
					tagElement.Value = YResolution.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute yResolutionAttribute = new XAttribute(XMPExifTagIDs.YResolutionID, YResolution.ToString());
					DescriptionElement.Add(yResolutionAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//ResolutionUnit
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ResolutionUnitID);
			tagElement = GetXMPElement(XMPExifTagIDs.ResolutionUnitID);
			if (ResolutionUnit != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = ResolutionUnit.ToString();
				if (tagElement != null)
					tagElement.Value = ResolutionUnit.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute resolutionUnitAttribute = new XAttribute(XMPExifTagIDs.ResolutionUnitID, ResolutionUnit.ToString());
					DescriptionElement.Add(resolutionUnitAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//DateTime
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.DateTimeID);
			tagElement = GetXMPElement(XMPExifTagIDs.DateTimeID);
			if (DateTime != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = GetXMPTagStringFromDateTime(DateTime);
				if (tagElement != null)
					tagElement.Value = GetXMPTagStringFromDateTime(DateTime);
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute dateTimeAttribute = new XAttribute(XMPExifTagIDs.DateTimeID, GetXMPTagStringFromDateTime(DateTime));
					DescriptionElement.Add(dateTimeAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//ImageDescription
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ImageDescriptionID);
			tagElement = GetXMPElement(XMPExifTagIDs.ImageDescriptionID);
			if (!String.IsNullOrEmpty(ImageDescription))
			{
				if (tagAttribute != null)
					tagAttribute.Value = ImageDescription;
				if (tagElement != null)
					tagElement.Value = ImageDescription;
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute imageDescriptionAttribute = new XAttribute(XMPExifTagIDs.ImageDescriptionID, ImageDescription);
					DescriptionElement.Add(imageDescriptionAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//Make
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.MakeID);
			tagElement = GetXMPElement(XMPExifTagIDs.MakeID);
			if (!String.IsNullOrEmpty(Make))
			{

				if (tagAttribute != null)
					tagAttribute.Value = Make;
				if (tagElement != null)
					tagElement.Value = Make;
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute makeAttribute = new XAttribute(XMPExifTagIDs.MakeID, Make);
					DescriptionElement.Add(makeAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//Model
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ModelID);
			tagElement = GetXMPElement(XMPExifTagIDs.ModelID);
			if (!String.IsNullOrEmpty(Model))
			{
				if (tagAttribute != null)
					tagAttribute.Value = Model;
				if (tagElement != null)
					tagElement.Value = Model;
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute modelAttribute = new XAttribute(XMPExifTagIDs.ModelID, Model);
					DescriptionElement.Add(modelAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//Software
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.SoftwareID);
			tagElement = GetXMPElement(XMPExifTagIDs.SoftwareID);
			if (!String.IsNullOrEmpty(Software))
			{
				if (tagAttribute != null)
					tagAttribute.Value = Software;
				if (tagElement != null)
					tagElement.Value = Software;
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute softwareAttribute = new XAttribute(XMPExifTagIDs.SoftwareID, Software);
					DescriptionElement.Add(softwareAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//Artist
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ArtistID);
			tagElement = GetXMPElement(XMPExifTagIDs.ArtistID);
			if (!String.IsNullOrEmpty(Artist))
			{
				if (tagAttribute != null)
					tagAttribute.Value = Artist;
				if (tagElement != null)
					tagElement.Value = Artist;
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute artistAttribute = new XAttribute(XMPExifTagIDs.ArtistID, Artist);
					DescriptionElement.Add(artistAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//Copyright
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.CopyrightID);
			tagElement = GetXMPElement(XMPExifTagIDs.CopyrightID);
			if (!String.IsNullOrEmpty(Copyright))
			{
				if (tagAttribute != null)
					tagAttribute.Value = Copyright;
				if (tagElement != null)
					tagElement.Value = Copyright;
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute copyrightAttribute = new XAttribute(XMPExifTagIDs.CopyrightID, Copyright);
					DescriptionElement.Add(copyrightAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//UserComment
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.UserCommentID);
			tagElement = GetXMPElement(XMPExifTagIDs.UserCommentID);
			if (!String.IsNullOrEmpty(UserComment))
			{
				if (tagAttribute != null)
					tagAttribute.Value = UserComment;
				if (tagElement != null)
					tagElement.Value = UserComment;
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute userCommentAttribute = new XAttribute(XMPExifTagIDs.UserCommentID, UserComment);
					DescriptionElement.Add(userCommentAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//DateTimeOriginal
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.DateTimeOriginalID);
			tagElement = GetXMPElement(XMPExifTagIDs.DateTimeOriginalID);
			if (DateTimeOriginal != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = GetXMPTagStringFromDateTime(DateTimeOriginal);
				if (tagElement != null)
					tagElement.Value = GetXMPTagStringFromDateTime(DateTimeOriginal);
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute dateTimeOriginalAttribute = new XAttribute(XMPExifTagIDs.DateTimeOriginalID, GetXMPTagStringFromDateTime(DateTimeOriginal));
					DescriptionElement.Add(dateTimeOriginalAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//DateTimeDigitized
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.DateTimeDigitizedID);
			tagElement = GetXMPElement(XMPExifTagIDs.DateTimeDigitizedID);
			if (DateTimeDigitized != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = GetXMPTagStringFromDateTime(DateTimeDigitized);
				if (tagElement != null)
					tagElement.Value = GetXMPTagStringFromDateTime(DateTimeDigitized);
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute dateTimeDigitizedAttribute = new XAttribute(XMPExifTagIDs.DateTimeDigitizedID, GetXMPTagStringFromDateTime(DateTimeDigitized));
					DescriptionElement.Add(dateTimeDigitizedAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//MeteringMode
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.MeteringModeID);
			tagElement = GetXMPElement(XMPExifTagIDs.MeteringModeID);
			if (MeteringMode != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = MeteringMode.ToString();
				if (tagElement != null)
					tagElement.Value = MeteringMode.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute meteringModeAttribute = new XAttribute(XMPExifTagIDs.MeteringModeID, MeteringMode.ToString());
					DescriptionElement.Add(meteringModeAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}


			//SensingMethod
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.SensingMethodID);
			tagElement = GetXMPElement(XMPExifTagIDs.SensingMethodID);
			if (SensingMethod != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = SensingMethod.ToString();
				if (tagElement != null)
					tagElement.Value = SensingMethod.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute sensingMethodAttribute = new XAttribute(XMPExifTagIDs.SensingMethodID, SensingMethod.ToString());
					DescriptionElement.Add(sensingMethodAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//ExposureProgram
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ExposureProgramID);
			tagElement = GetXMPElement(XMPExifTagIDs.ExposureProgramID);
			if (ExposureProgram != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = ExposureProgram.ToString();
				if (tagElement != null)
					tagElement.Value = ExposureProgram.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute exposureProgramAttribute = new XAttribute(XMPExifTagIDs.ExposureProgramID, ExposureProgram.ToString());
					DescriptionElement.Add(exposureProgramAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//ISOSpeedRatings
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ISOSpeedRatingsID);
			tagElement = GetXMPElement(XMPExifTagIDs.ISOSpeedRatingsID);
			if (ISOSpeedRatings != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = ISOSpeedRatings.ToString();
				if (tagElement != null)
					tagElement.Value = ISOSpeedRatings.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute isoSpeedRatingsAttribute = new XAttribute(XMPExifTagIDs.ISOSpeedRatingsID, ISOSpeedRatings.ToString());
					DescriptionElement.Add(isoSpeedRatingsAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//LightSource
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.LightSourceID);
			tagElement = GetXMPElement(XMPExifTagIDs.LightSourceID);
			if (LightSource != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = LightSource.ToString();
				if (tagElement != null)
					tagElement.Value = LightSource.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute lightSourceAttribute = new XAttribute(XMPExifTagIDs.LightSourceID, LightSource.ToString());
					DescriptionElement.Add(lightSourceAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//ExposureTime
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ExposureTimeID);
			tagElement = GetXMPElement(XMPExifTagIDs.ExposureTimeID);
			if (ExposureTime != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = ExposureTime.ToString();
				if (tagElement != null)
					tagElement.Value = ExposureTime.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute exposureTimeAttribute = new XAttribute(XMPExifTagIDs.ExposureTimeID, ExposureTime.ToString());
					DescriptionElement.Add(exposureTimeAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}


			//Fnumber
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.FNumberID);
			tagElement = GetXMPElement(XMPExifTagIDs.FNumberID);
			if (FNumber != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = FNumber.ToString();
				if (tagElement != null)
					tagElement.Value = FNumber.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute fNumberAttribute = new XAttribute(XMPExifTagIDs.FNumberID, FNumber.ToString());
					DescriptionElement.Add(fNumberAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//ShutterSpeed
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ShutterSpeedID);
			tagElement = GetXMPElement(XMPExifTagIDs.ShutterSpeedID);
			if (ShutterSpeed != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = ShutterSpeed.ToString();
				if (tagElement != null)
					tagElement.Value = ShutterSpeed.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute shutterSpeedAttribute = new XAttribute(XMPExifTagIDs.ShutterSpeedID, ShutterSpeed.ToString());
					DescriptionElement.Add(shutterSpeedAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//Brightness
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.BrightnessID);
			tagElement = GetXMPElement(XMPExifTagIDs.BrightnessID);
			if (Brightness != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = Brightness.ToString();
				if (tagElement != null)
					tagElement.Value = Brightness.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute brightnessAttribute = new XAttribute(XMPExifTagIDs.BrightnessID, Brightness.ToString());
					DescriptionElement.Add(brightnessAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}

			//Aperture
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ApertureID);
			tagElement = GetXMPElement(XMPExifTagIDs.ApertureID);
			if (Aperture != null)
			{
				if (tagAttribute != null)
					tagAttribute.Value = Aperture.ToString();
				if (tagElement != null)
					tagElement.Value = Aperture.ToString();
				if (tagElement == null && tagAttribute == null)
				{
					XAttribute apertureAttribute = new XAttribute(XMPExifTagIDs.ApertureID, Aperture.ToString());
					DescriptionElement.Add(apertureAttribute);
				}
			}
			else
			{
				if (tagAttribute != null)
					tagAttribute.Remove();
				if (tagElement != null)
					tagElement.Remove();
			}
		}

		
		

		#endregion


		#region load

		/// <summary>
		/// Load an Image
		/// </summary>
		/// <remarks>
		/// Defaults all tags to be loaded with a precedence of XMPExif > XMP
		/// </remarks>
		/// <param name="filePath">The file path of the Image to load</param>
		public override void Load(string filePath)
		{
			FilePath = filePath;
			Load(filePath, false, true, true, TagPrecedence.XMPExif_XMP_Exif);
		}


		/// <summary>
		/// Load an Image
		/// </summary>
		/// <param name="filePath">The file path of the Image to load</param>
		/// <param name="loadExif">Not used in PNGs as PNGs do not have Exif, here for inheritance reasons</param>
		/// <param name="loadXMPExif">Whether or not to load XMPExif tags</param>
		/// <param name="loadXMP">Whether or not to load XMP tags</param>
		/// <param name="precendenceOfTags">The precendece of the tagging systems (used for conflicts)</param>
		public override void Load(string filePath, bool loadExif, bool loadXMPExif, bool loadXMP, TagPrecedence precendenceOfTags)
		{
			Precedence = precendenceOfTags;
			Debug.Assert(ExtensionChecker.IsPNGExtension(System.IO.Path.GetExtension(filePath)),
				"PngTagger can only load a png file");
			FilePath = filePath;
			

			using (Stream originalFile = File.Open(FilePath, FileMode.Open, FileAccess.Read))
			{
				BitmapDecoder originalImage = BitmapDecoder.Create(originalFile, BitmapOptions, BitmapCacheOption.None);
				var metadata = originalImage.Frames[0].Metadata.Clone() as BitmapMetadata;


				//Get the XmpXml

				string xmlString = (string)metadata.GetQuery(XMP_XML_QUERY);

				if(String.IsNullOrEmpty(xmlString))
				{
					XmpXML = CreateNewXMPDocument();
				}
				else
				{
					XmpXML = XDocument.Parse(xmlString);
				}

				//Image Width
				ImageWidth = (uint)originalImage.Frames[0].PixelWidth;

				//Image Height 
				ImageHeight = (uint)originalImage.Frames[0].PixelHeight;
			}

			//XMP has precedence
			if (Precedence == TagPrecedence.Exif_XMP_XMPExif ||
				Precedence == TagPrecedence.XMP_Exif_XMPExif ||
				Precedence == TagPrecedence.XMP_XMPExif_Exif)
			{
				if (loadXMPExif)
					LoadXMPExif();
				if (loadXMP)
					LoadXMP();
			}
			//XMPExif has precedence
			else if (Precedence == TagPrecedence.Exif_XMPExif_XMP ||
					Precedence == TagPrecedence.XMPExif_Exif_XMP ||
					Precedence == TagPrecedence.XMPExif_XMP_Exif)
			{
				if (loadXMP)
					LoadXMP();
				if (loadXMPExif)
					LoadXMPExif();
			}
			else
			{
				throw new Exception("Unknown TagPrecedence");
			}
			
		}

		/// <summary>
		/// Load the XMPExif tags, will overwrite any tag data already loaded that conflicts
		/// </summary>
		/// <example>
		/// If author already loaded from XMP, and author is found in XMPExif, the XMPExif
		/// tag value will replace it
		/// </example>
		private void LoadXMPExif()
		{
			XElement tagElement = null;
			XAttribute tagAttribute = null;

			//XResolution
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.XResolutionID);
			if (tagAttribute != null)
				XResolution = new URational(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.XResolutionID);
				if (tagElement != null)
					XResolution = new URational(tagElement.Value);
			}

			//YResolution
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.YResolutionID);
			if (tagAttribute != null)
				YResolution = new URational(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.YResolutionID);
				if (tagElement != null)
					YResolution = new URational(tagElement.Value);
			}


			//ResolutionUnit
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ResolutionUnitID);
			if (tagAttribute != null)
				ResolutionUnit = Convert.ToUInt16(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.ResolutionUnitID);
				if (tagElement != null)
					ResolutionUnit = Convert.ToUInt16(tagElement.Value);
			}

			//DateTime
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.DateTimeID);
			if (tagAttribute != null)
				DateTime = GetDateTimeFromXMPString(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.DateTimeID);
				if (tagElement != null)
					DateTime = GetDateTimeFromXMPString(tagElement.Value);

			}

			//ImageDescription
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ImageDescriptionID);
			if (tagAttribute != null)
				ImageDescription = tagAttribute.Value;
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.ImageDescriptionID);
				if (tagElement != null)
					ImageDescription = tagElement.Value;
			}

			//Make
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.MakeID);
			if (tagAttribute != null)
				Make = tagAttribute.Value;
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.MakeID);
				if (tagElement != null)
					Make = tagElement.Value;
			}

			//Model
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ModelID);
			if (tagAttribute != null)
				Model = tagAttribute.Value;
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.ModelID);
				if (tagElement != null)
					Model = tagElement.Value;
			}

			//Software
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.SoftwareID);
			if (tagAttribute != null)
				Software = tagAttribute.Value;
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.SoftwareID);
				if (tagElement != null)
					Software = tagElement.Value;
			}

			//Artist
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ArtistID);
			if (tagAttribute != null)
				Artist = tagAttribute.Value;
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.ArtistID);
				if (tagElement != null)
					Artist = tagElement.Value;
			}

			//Copyright
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.CopyrightID);
			if (tagAttribute != null)
				Copyright = tagAttribute.Value;
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.CopyrightID);
				if (tagElement != null)
					Copyright = tagElement.Value;
			}

			//UserComment
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.UserCommentID);
			if (tagAttribute != null)
				UserComment = tagAttribute.Value;
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.UserCommentID);
				if (tagElement != null)
					UserComment = tagElement.Value;
			}

			//DateTimeOriginal
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.DateTimeOriginalID);
			if (tagAttribute != null)
				DateTimeOriginal = GetDateTimeFromXMPString(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.DateTimeOriginalID);
				if (tagElement != null)
					DateTimeOriginal = GetDateTimeFromXMPString(tagElement.Value);
			}

			//DateTimeDigitized
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.DateTimeDigitizedID);
			if (tagAttribute != null)
				DateTimeDigitized = GetDateTimeFromXMPString(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.DateTimeDigitizedID);
				if (tagElement != null)
					DateTimeDigitized = GetDateTimeFromXMPString(tagElement.Value);
			}

			//MeteringMode
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.MeteringModeID);
			if (tagAttribute != null)
				MeteringMode = Convert.ToUInt16(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.MeteringModeID);
				if (tagElement != null)
					MeteringMode = Convert.ToUInt16(tagElement.Value);
			}


			//SensingMethod
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.SensingMethodID);
			if (tagAttribute != null)
				SensingMethod = Convert.ToUInt16(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.SensingMethodID);
				if (tagElement != null)
					SensingMethod = Convert.ToUInt16(tagElement.Value);
			}

			//ExposureProgram
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ExposureProgramID);
			if (tagAttribute != null)
				ExposureProgram = Convert.ToUInt16(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.ExposureProgramID);
				if (tagElement != null)
					ExposureProgram = Convert.ToUInt16(tagElement.Value);
			}

			//ISOSpeedRatings
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ISOSpeedRatingsID);
			if (tagAttribute != null)
				ISOSpeedRatings = Convert.ToUInt16(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.ISOSpeedRatingsID);
				if (tagElement != null)
					ISOSpeedRatings = Convert.ToUInt16(tagElement.Value);
			}

			//LightSource
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.LightSourceID);
			if (tagAttribute != null)
				LightSource = Convert.ToUInt16(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.LightSourceID);
				if (tagElement != null)
					LightSource = Convert.ToUInt16(tagElement.Value);
			}

			//ExposureTime
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ExposureTimeID);
			if (tagAttribute != null)
				ExposureTime = new URational(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.ExposureTimeID);
				if (tagElement != null)
					ExposureTime = new URational(tagElement.Value);
			}


			//Fnumber
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.FNumberID);
			if (tagAttribute != null)
				FNumber = new URational(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.FNumberID);
				if (tagElement != null)
					FNumber = new URational(tagElement.Value);
			}

			//ShutterSpeed
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ShutterSpeedID);
			if (tagAttribute != null)
				ShutterSpeed = new Rational(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.ShutterSpeedID);
				if (tagElement != null)
					ShutterSpeed = new Rational(tagElement.Value);
			}

			//Brightness
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.BrightnessID);
			if (tagAttribute != null)
				Brightness = new Rational(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.BrightnessID);
				if (tagElement != null)
					Brightness = new Rational(tagElement.Value);
			}

			//Aperture
			tagAttribute = GetXMPAttribute(XMPExifTagIDs.ApertureID);
			if (tagAttribute != null)
				Aperture = new URational(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPExifTagIDs.ApertureID);
				if (tagElement != null)
					Aperture = new URational(tagElement.Value);
			}
		}
		
		

		/// <summary>
		/// Load the XMP tags, will overwrite any tag data already loaded that conflicts
		/// </summary>
		/// <example>
		/// If author already loaded from XMPExif, and author is found in XMP, the XMP
		/// tag value will replace it
		/// </example>
		private void LoadXMP()
		{


			XElement tagElement = null;
			XAttribute tagAttribute = null;

			//DateTime
			tagAttribute = GetXMPAttribute(XMPTagIDs.ModifyDateID);
			if (tagAttribute != null)
				DateTime = GetDateTimeFromXMPString(tagAttribute.Value);
			else
			{
				tagElement = GetXMPElement(XMPTagIDs.ModifyDateID);
				if (tagElement != null)
					DateTime = GetDateTimeFromXMPString(tagElement.Value);
			}
			

			//Image Description 
			tagElement = GetXMPElement(XMPTagIDs.DescriptionID);
			if (tagElement != null)
			{
				//The tag does exist
				XElement englishElement = GetLangAltEnglishElement(tagElement);
				if (englishElement != null)
				{
					ImageDescription = englishElement.Value;
				}
			}
			

			//Software
			tagAttribute = GetXMPAttribute(XMPTagIDs.CreatorToolID);
			if (tagAttribute != null)
				Software = tagAttribute.Value;
			else
			{
				tagElement = GetXMPElement(XMPTagIDs.CreatorToolID);
				if (tagElement != null)
					Software = tagElement.Value;
			}

			//Artist 
			tagAttribute = GetXMPAttribute(XMPTagIDs.CreatorID);
			if (tagAttribute != null)
				Artist = tagAttribute.Value;
			else
			{
				tagElement = GetXMPElement(XMPTagIDs.CreatorID);
				if (tagElement != null)
					Artist = tagElement.Value;
			}


			//Copyright RightsID
			tagElement = GetXMPElement(XMPTagIDs.RightsID);
			if (tagElement != null)
			{
				//The tag does exist
				XElement englishElement = GetLangAltEnglishElement(tagElement);
				if (englishElement != null)
				{
					Copyright = englishElement.Value;
				}
			}
		}

		#endregion


		/// <summary>
		/// Get the XMP element with the passed element name
		/// </summary>
		/// <param name="elementName">The name of the element to get</param>
		/// <returns>The element if it exists, null if it does not</returns>
		private  XElement GetXMPElement(XName elementName)
		{
			XElement element = null;
			foreach (var description in DescriptionElements)
			{
				element = description.Element(elementName);

				if (element != null)
					break;
			}
			return element;
		}

		/// <summary>
		/// Get the english element from the LangAlt elemnt
		/// </summary>
		/// <param name="langAltTag">The LangAlt element to get the english elemtn from</param>
		/// <returns>The english element if it exists, null if not</returns>
		private XElement GetLangAltEnglishElement(XElement langAltTag)
		{
			Debug.Assert(langAltTag != null, "langAltTag was null");
			XElement langAltElement= langAltTag.Element(XMPTagIDs.LangAltID);
			
			if (langAltElement != null)
			{
				//Get the english element
				XAttribute languageAttribute;
				foreach (XElement listItem in langAltElement.Elements(XMPTagIDs.ListElementID))
				{
					languageAttribute = listItem.Attribute(XMPTagIDs.LanguageID);
					if (languageAttribute != null)
					{
						if(languageAttribute.Value.Equals("en"))
							return listItem;
					}
						
				}
			}
			return null;

		}

		/// <summary>
		/// Create a new LangAlt XMP with the passed name and the passed english value
		/// </summary>
		/// <param name="LangAltName">The name of the Lang Alt Element</param>
		/// <param name="englishValue">The value of the english value</param>
		/// <returns>The newly created Lang Alt element</returns>
		private XElement CreateLangAltWithEnglishElement(XName LangAltName, string englishValue)
		{
			XElement altLangTag = new XElement(LangAltName,
				new XElement(XMPTagIDs.LangAltID,
					new XElement(XMPTagIDs.ListElementID,
						new XAttribute(XMPTagIDs.LanguageID, "en"),
						new XText(englishValue))));
			return altLangTag;
		}

		/// <summary>
		/// Add the passed english value to the passed Lang Alt element
		/// </summary>
		/// <param name="langAltElement">The Lang Alt element</param>
		/// <param name="englishValue">The english value</param>
		private void AddEnglishElementToLangAltElement(XElement langAltElement, string englishValue)
		{
			XElement langAltTag = langAltElement.Element(XMPTagIDs.LangAltID);
			Debug.Assert(langAltTag != null, "passed langAltElement is not a lang alt element");

			langAltTag.Add(new XElement(XMPTagIDs.ListElementID,
				new XAttribute(XMPTagIDs.LanguageID, "en"),
				new XText(englishValue)));
		}

		/// <summary>
		/// Get the XMP attribute with the passed attribute name
		/// </summary>
		/// <param name="attributeName">The name of the attribute to get</param>
		/// <returns>The attribute if it exists, null if it does not</returns>
		private XAttribute GetXMPAttribute(XName attributeName)
		{
			XAttribute attribute = null;
			foreach (var description in DescriptionElements)
			{
				attribute = description.Attribute(attributeName);
			}
			return attribute;
		}



		/// <summary>
		/// Get the XML XMP data
		/// </summary>
		/// <returns>The XMP data in XML</returns>
		public override string ToString()
		{
			if (XmpXML == null)
			{
				return "";
			}
			else
			{
				return XmpXML.ToString(SaveOptions.None);
			}
		}

		/// <summary>
		/// Create a new empty XMP XML document
		/// </summary>
		/// <remarks>
		/// Empty XMP document will have the following structure
		/// 
		/// xmpmeta
		///		RDF
		///			description
		///			/description
		///		
		///		/RDF
		/// /xmpmeta
		/// </remarks>
		/// <returns>A new empty XMP XML document</returns>
		private XDocument CreateNewXMPDocument()
		{
			XDocument document = new XDocument();

			//Create xmp metadata element
			XElement metadataElement = new XElement(XMPElementNames.Metadata);
			metadataElement.Add(new XAttribute(XNamespace.Xmlns + XMPPrefixes.MetadataPrefix, XMPNamespaces.MetadataNamespace.NamespaceName));
			document.Add(metadataElement);

			//Create RDF element
			XElement rdfElement = new XElement(XMPElementNames.RDF);
			rdfElement.Add(new XAttribute(XNamespace.Xmlns + XMPPrefixes.RDFPrefix, XMPNamespaces.RDFNamespace.NamespaceName));
			metadataElement.Add(rdfElement);

			//Create description element
			XElement descriptionElement = new XElement(XMPElementNames.Description);
			rdfElement.Add(descriptionElement);


			return document;
		}
	}
}
